#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>

cv::Mat my_copy_make_border(const cv::Mat& mat, int top, int bottom, int left, int right, int borderType, uchar val = 0) {
    CV_Assert(mat.type() == CV_8UC1);
    
    cv::Mat result(mat.rows + top + bottom, mat.cols + left + right, mat.type());
    cv::Mat mat_center = cv::Mat(result, cv::Rect(left, top, mat.cols, mat.rows));
    cv::Mat mat_top = cv::Mat(result, cv::Rect(left, 0, mat.cols, top));
    cv::Mat mat_bottom = cv::Mat(result, cv::Rect(left, mat.rows + top, mat.cols, bottom));
    cv::Mat mat_left = cv::Mat(result, cv::Rect(0, 0, left, result.rows));
    cv::Mat mat_right = cv::Mat(result, cv::Rect(mat.cols + left, 0, right, result.rows));
    
    mat.copyTo(mat_center);
    switch (borderType) {
        case cv::BORDER_CONSTANT: {
            mat_top = val;
            mat_bottom = val;
            mat_left = val;
            mat_right = val;
            break;
        }
        case cv::BORDER_REPLICATE: {
            cv::Mat mat_top_row = cv::Mat(mat, cv::Rect(0, 0, mat.cols, 1));
            cv::Mat mat_bottom_row = cv::Mat(mat, cv::Rect(0, mat.rows - 1, mat.cols, 1));
            cv::Mat mat_left_col = cv::Mat(result, cv::Rect(left, 0, 1, result.rows));
            cv::Mat mat_right_col = cv::Mat(result, cv::Rect(result.cols - 1 - right, 0, 1, result.rows));
            for (int i = 0; i < top; i++) {
                mat_top_row.copyTo(cv::Mat(mat_top, cv::Rect(0, i, mat.cols, 1)));
            }
            for (int i = 0; i < bottom; i++) {
                mat_bottom_row.copyTo(cv::Mat(mat_bottom, cv::Rect(0, i, mat.cols, 1)));
            }
            for (int i = 0; i < left; i++) {
                mat_left_col.copyTo(cv::Mat(mat_left, cv::Rect(i, 0, 1, result.rows)));
            }
            for (int i = 0; i < right; i++) {
                mat_right_col.copyTo(cv::Mat(mat_right, cv::Rect(i, 0, 1, result.rows)));
            }
            break;
        }
        case cv::BORDER_REFLECT: {
            for (int i = 0; i < top; i++) {
                cv::Mat mat_top_row = cv::Mat(mat, cv::Rect(0, i, mat.cols, 1));
                mat_top_row.copyTo(cv::Mat(mat_top, cv::Rect(0, top - 1 - i, mat.cols, 1)));
            }
            for (int i = 0; i < bottom; i++) {
                cv::Mat mat_bottom_row = cv::Mat(mat, cv::Rect(0, mat.rows - 1 - i, mat.cols, 1));
                mat_bottom_row.copyTo(cv::Mat(mat_bottom, cv::Rect(0, i, mat.cols, 1)));
            }
            for (int i = 0; i < left; i++) {
                cv::Mat mat_left_col = cv::Mat(result, cv::Rect(left + i, 0, 1, result.rows));
                mat_left_col.copyTo(cv::Mat(result, cv::Rect(left - i - 1, 0, 1, result.rows)));
            }
            for (int i = 0; i < right; i++) {
                cv::Mat mat_right_col = cv::Mat(result, cv::Rect(result.cols - right - 1 - i, 0, 1, result.rows));
                mat_right_col.copyTo(cv::Mat(result, cv::Rect(result.cols - right + i, 0, 1, result.rows)));
            }
            break;
        }
        case cv::BORDER_WRAP: {
            for (int i = 0; i < top; i++) {
                cv::Mat mat_bottom_row = cv::Mat(mat, cv::Rect(0, mat.rows - 1 - i, mat.cols, 1));
                mat_bottom_row.copyTo(cv::Mat(mat_top, cv::Rect(0, top - 1 - i, mat.cols, 1)));
            }
            for (int i = 0; i < bottom; i++) {
                cv::Mat mat_top_row = cv::Mat(mat, cv::Rect(0, i, mat.cols, 1));
                mat_top_row.copyTo(cv::Mat(mat_bottom, cv::Rect(0, i, mat.cols, 1)));
            }
            for (int i = 0; i < left; i++) {
                cv::Mat mat_right_col = cv::Mat(result, cv::Rect(result.cols - right - 1 - i, 0, 1, result.rows));
                mat_right_col.copyTo(cv::Mat(result, cv::Rect(left - i - 1, 0, 1, result.rows)));
            }
            for (int i = 0; i < right; i++) {
                cv::Mat mat_left_col = cv::Mat(result, cv::Rect(left + i, 0, 1, result.rows));
                mat_left_col.copyTo(cv::Mat(result, cv::Rect(result.cols - right + i, 0, 1, result.rows)));
            }
            break;
        }
        case cv::BORDER_REFLECT101: {
            for (int i = 0; i < top; i++) {
                cv::Mat mat_top_row = cv::Mat(mat, cv::Rect(0, i+1, mat.cols, 1));
                mat_top_row.copyTo(cv::Mat(mat_top, cv::Rect(0, top - 1 - i, mat.cols, 1)));
            }
            for (int i = 0; i < bottom; i++) {
                cv::Mat mat_bottom_row = cv::Mat(mat, cv::Rect(0, mat.rows - 2 - i, mat.cols, 1));
                mat_bottom_row.copyTo(cv::Mat(mat_bottom, cv::Rect(0, i, mat.cols, 1)));
            }
            for (int i = 0; i < left; i++) {
                cv::Mat mat_left_col = cv::Mat(result, cv::Rect(left+1+i, 0, 1, result.rows));
                mat_left_col.copyTo(cv::Mat(result, cv::Rect(left-i-1, 0, 1, result.rows)));
            }
            for (int i = 0; i < right; i++) {
                cv::Mat mat_right_col = cv::Mat(result, cv::Rect(result.cols-right-2-i, 0, 1, result.rows));
                mat_right_col.copyTo(cv::Mat(result, cv::Rect(result.cols-right+i, 0, 1, result.rows)));
            }
            break;
        }
        case cv::BORDER_TRANSPARENT:
        case cv::BORDER_ISOLATED:
            CV_Error(-1, "Not supported!");
            break;
        default:
            break;
    }
    return result;
}

int main(int argc, char **argv) {
    cv::Mat mat(5, 6, CV_8UC1);
    cv::randu(mat, cv::Scalar(0), cv::Scalar(255));
    
    int top = 2, bottom = 3, left = 2, right = 3;
    
    /*
    cv::Mat mat_constant, my_mat_constant;
    cv::copyMakeBorder(mat, mat_constant, top, bottom, left, right, cv::BORDER_CONSTANT, cv::Scalar(100));
    my_mat_constant = my_copy_make_border(mat, top, bottom, left, right, cv::BORDER_CONSTANT, 100);
    cv::Mat constant_diff = mat_constant != my_mat_constant;
    std::vector<cv::Point> constant_nonzeros;
    cv::findNonZero(constant_diff, constant_nonzeros);
    std::cout << "mat = \n" << mat << std::endl;
    std::cout << "mat_constant = \n" << mat_constant << std::endl;
    std::cout << "my_mat_constant = \n" << my_mat_constant << std::endl;
    std::cout << "constant_nonzeros = " << constant_nonzeros << std::endl;
    */
    
    /*
    cv::Mat mat_replicate, my_mat_replicate;
    cv::copyMakeBorder(mat, mat_replicate, top, bottom, left, right, cv::BORDER_REPLICATE, cv::Scalar(100));
    my_mat_replicate = my_copy_make_border(mat, top, bottom, left, right, cv::BORDER_REPLICATE, 100);
    cv::Mat replicate_diff = mat_replicate != my_mat_replicate;
    std::vector<cv::Point> replicate_nonzeros;
    cv::findNonZero(replicate_diff, replicate_nonzeros);
    std::cout << "mat = \n" << mat << std::endl;
    std::cout << "mat_replicate = \n" << mat_replicate << std::endl;
    std::cout << "my_mat_replicate = \n" << my_mat_replicate << std::endl;
    std::cout << "replicate_nonzeros = " << replicate_nonzeros << std::endl;
    */
    
    /*
    cv::Mat mat_reflect, my_mat_reflect;
    cv::copyMakeBorder(mat, mat_reflect, top, bottom, left, right, cv::BORDER_REFLECT, cv::Scalar(100));
    my_mat_reflect = my_copy_make_border(mat, top, bottom, left, right, cv::BORDER_REFLECT, 100);
    cv::Mat reflect_diff = mat_reflect != my_mat_reflect;
    std::vector<cv::Point> reflect_nonzeros;
    cv::findNonZero(reflect_diff, reflect_nonzeros);
    std::cout << "mat = \n" << mat << std::endl;
    std::cout << "mat_reflect = \n" << mat_reflect << std::endl;
    std::cout << "my_mat_reflect = \n" << my_mat_reflect << std::endl;
    std::cout << "reflect_nonzeros = " << reflect_nonzeros << std::endl;
    */
    
    /*
    cv::Mat mat_wrap, my_mat_wrap;
    cv::copyMakeBorder(mat, mat_wrap, top, bottom, left, right, cv::BORDER_WRAP, cv::Scalar(100));
    my_mat_wrap = my_copy_make_border(mat, top, bottom, left, right, cv::BORDER_WRAP, 100);
    cv::Mat wrap_diff = mat_wrap != my_mat_wrap;
    std::vector<cv::Point> wrap_nonzeros;
    cv::findNonZero(wrap_diff, wrap_nonzeros);
    std::cout << "mat = \n" << mat << std::endl;
    std::cout << "mat_wrap = \n" << mat_wrap << std::endl;
    std::cout << "my_mat_wrap = \n" << my_mat_wrap << std::endl;
    std::cout << "wrap_nonzeros = " << wrap_nonzeros << std::endl;
    */
    
    /*
    cv::Mat mat_reflect101, my_mat_reflect101;
    cv::copyMakeBorder(mat, mat_reflect101, top, bottom, left, right, cv::BORDER_REFLECT101, cv::Scalar(100));
    my_mat_reflect101 = my_copy_make_border(mat, top, bottom, left, right, cv::BORDER_REFLECT101, 100);
    cv::Mat reflect101_diff = mat_reflect101 != my_mat_reflect101;
    std::vector<cv::Point> reflect101_nonzeros;
    cv::findNonZero(reflect101_diff, reflect101_nonzeros);
    std::cout << "mat = \n" << mat << std::endl;
    std::cout << "mat_reflect101 = \n" << mat_reflect101 << std::endl;
    std::cout << "my_mat_reflect101 = \n" << my_mat_reflect101 << std::endl;
    std::cout << "reflect101_nonzeros = " << reflect101_nonzeros << std::endl;
    */
    
    /*
    cv::Mat translateMat = (cv::Mat_<float>(2, 3) << 1, 0, 2, 0, 1, 3);  // x -> 2, y -> 3
    cv::Mat mat_transparent = cv::Mat::zeros(mat.size(), mat.type());
    cv::Mat mat_constant_200 = cv::Mat::zeros(mat.size(), mat.type());
    mat_transparent = 100;
    mat_constant_200 = 100;
    std::cout << "mat_transparent = \n" << mat_transparent << std::endl;
    std::cout << "mat_constant_200 = \n" << mat_constant_200 << std::endl;
    cv::warpAffine(mat, mat_transparent, translateMat, mat.size(), cv::INTER_LINEAR, cv::BORDER_TRANSPARENT, cv::Scalar(200));
    cv::warpAffine(mat, mat_constant_200, translateMat, mat.size(), cv::INTER_LINEAR, cv::BORDER_CONSTANT, cv::Scalar(200));
    std::cout << "mat = \n" << mat << std::endl;
    std::cout << "mat_transparent after = \n" << mat_transparent << std::endl;
    std::cout << "mat_constant_200 after = \n" << mat_constant_200 << std::endl;
    */
    
    cv::Mat matROI = cv::Mat(mat, cv::Rect(2, 2, 3, 3));
    cv::Mat mat_roi_constant, mat_roi_constant_isolated;
    cv::copyMakeBorder(matROI, mat_roi_constant, top, bottom, left, right, cv::BORDER_CONSTANT, cv::Scalar(100));
    cv::copyMakeBorder(matROI, mat_roi_constant_isolated, top, bottom, left, right, cv::BORDER_CONSTANT | cv::BORDER_ISOLATED, cv::Scalar(100));
    std::cout << "mat = \n" << mat << std::endl;
    std::cout << "matROI = \n" << matROI << std::endl;
    std::cout << "mat_roi_constant = \n" << mat_roi_constant << std::endl;
    std::cout << "mat_roi_constant_isolated = \n" << mat_roi_constant_isolated << std::endl;
    
    
    return 0;
}
